package org.lex.collection.tree;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import org.lex.string.DefaultPrinter;
import org.lex.string.IFormatter;
import org.lex.util.DataHolder;

public class TreeNode extends DataHolder implements ITreeNode {
	private static final ITreeNode[] EMPTY = new ITreeNode[0];

	protected ITreeNode parent = null;
	protected List<ITreeNode> children = new ArrayList<ITreeNode>();

	public static final String DEFAULT_STEP = "  ";

	public static final String DEFAULT_IDENT = "";

	public TreeNode() {
	}

	public TreeNode(Object data) {
		this.setData(data);
	}

	@Override
	public void accept(ITreeVisitor visitor) {
		visitor.visit(this);
		for (ITreeNode n : children) {
			n.accept(visitor);
		}
	}

	public void addChild(ITreeNode child) {
		this.insertChild(child, children.size());
	}

	@Override
	public void insertChild(ITreeNode child, int index) {
		if (this == child.getParent() && children.contains(child)) {
			// already child of this node, nothing to do
			return;
		}
		if (null != child.getParent()) {
			child.getParent().removeChild(child);
		}
		if (false == children.contains(child)) {
			children.listIterator(index).add(child);
		}
		child.setParent(this);
	}

	public ITreeNode[] getChildren() {
		return children.toArray(EMPTY);
	}

	@Override
	public int getChildCount() {
		return children.size();
	}

	@Override
	public ITreeNode getChild(int index) {
		return children.get(index);
	}

	public boolean hasChildren() {
		return false == children.isEmpty();
	}

	public ITreeNode getParent() {
		return parent;
	}

	public void setParent(ITreeNode parent) {
		if (parent != this.parent) {
			if (null != this.parent) {
				this.parent.removeChild(this);
			}
			if (null != parent && null == new TreeNodeHelper().searchChild(parent, this.getData())) {
				parent.addChild(this);
			}
		}
		this.parent = parent;
	}

	public void swap(int a, int b) {
		ITreeNode nodeA = children.get(a);
		ITreeNode tmp = nodeA;
		children.set(a, children.get(b));
		children.set(b, tmp);
	}

	@Override
	public void removeChild(int index) {
		ITreeNode node = children.remove(index);
		if (null != node) {
			node.setParent(null);
		}
	}

	public void removeChild(ITreeNode child) {
		boolean result = children.remove(child);
		if (true == result) {
			child.setParent(null);
		}
	}

	public void dispose() {
	}

	@Override
	public void print(String ident, String step, IFormatter<Object> dataPrinter, PrintWriter out) {
		print(ident, step, true, dataPrinter, out);
	}

	public void print(String ident, String step, PrintWriter out) {
		print(ident, step, true, new DefaultPrinter<Object>(), out);
	}

	@Override
	public String toTreeString() {
		return TreeUtils.print(this);
	}

	private void print(String ident, String step, boolean isLastChild, IFormatter<Object> dataPrinter, PrintWriter out) {
		// print self
		out.print(ident);
		if (true == isLastChild) {
			out.print("\\--");
		} else {
			out.print("+--");
		}
		dataPrinter.format(getData(), out);
		out.println();
		// print children
		if (true == this.hasChildren()) {
			int size = this.getChildCount();
			ident += step;
			for (int i = 0; i < size; i++) {
				boolean isLast = (i == (size - 1));
				((TreeNode) this.getChild(i)).print(ident, step, isLast, dataPrinter, out);
			}
		}
		out.flush();
	}

	public String toString() {
		return null == getData() ? "" : getData().toString();
	}
}
